﻿//------------------//---------------------------------------------------------------------------------
// Copyright (c) 2013, Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//---------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Microsoft.Azure.E2E.Common
{
    /// <summary>
    /// Helper base class for type safe constants
    /// </summary>
    /// <typeparam name="TConstant">The real type of the type safe constant (must be sealed and have a public constructor accepting a single argument of type <typeparamref name="TUnderlyingType"/> if adhoc construction is required)</typeparam>
    /// <typeparam name="TUnderlyingType">The backing type of the constant</typeparam>
    public abstract class TypeSafeConstantBase<TConstant, TUnderlyingType> : IEquatable<TConstant>
        where TConstant : TypeSafeConstantBase<TConstant, TUnderlyingType>
        where TUnderlyingType : IEquatable<TUnderlyingType>
    {
        /// <summary>
        /// The constructor for the constant type
        /// </summary>
        // ReSharper disable once StaticFieldInGenericType
        private static readonly ConstructorInfo ConstantConstructor = typeof(TConstant).GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(TUnderlyingType));

        /// <summary>
        /// Lookup conversions from underlying values to constant types, used by <see cref="explicit operator TUnderlyingType(TypeSafeConstantBase{TConstant, TUnderlyingType})"/>
        /// </summary>
        private static readonly Lazy<IDictionary<TUnderlyingType, TConstant>> Conversions;

        /// <summary>
        /// Lookup for names, used by <see cref="ToString"/>
        /// </summary>
        private static readonly Lazy<IDictionary<TConstant, string>> NameLookup;

        /// <summary>
        /// Lookup for constants by name, used by <see cref="Parse"/> and <see cref="TryParse"/>
        /// </summary>
        private static readonly Lazy<IDictionary<string, TConstant>> ParseLookup;

        /// <summary>
        /// The set of distinct values for the set
        /// </summary>
        private static readonly HashSet<TUnderlyingType> ValuesStore = new HashSet<TUnderlyingType>();

        /// <summary>
        /// The null valued constant, if available
        /// </summary>
        private static TConstant _nullValuedConstant;

        /// <summary>
        /// The originally computed hash code, used by <see cref="GetHashCode"/>
        /// </summary>
        private readonly int _hashCode;

        /// <summary>
        /// The underlying value, used by <see cref="Convert"/>
        /// </summary>
        private readonly TUnderlyingType _value;

        /// <summary>
        /// Static initializer, gets the fields of the derived type, filters down to the appropriately declared members and sets up the lookups
        /// </summary>
        static TypeSafeConstantBase()
        {
            var constantType = typeof(TConstant);
            RuntimeHelpers.RunClassConstructor(constantType.TypeHandle);
            var fieldCandidates = constantType.GetFields(BindingFlags.Public | BindingFlags.Static);
            var fields = fieldCandidates.Where(field => field.FieldType == constantType && field.Attributes.HasFlag(FieldAttributes.InitOnly)).ToList();

            ParseLookup = new Lazy<IDictionary<string, TConstant>>(() =>
            {
                var dict = new Dictionary<string, TConstant>();
                foreach (var field in fields)
                {
                    dict[field.Name] = (TConstant)field.GetValue(null);
                }
                return dict;
            });

            Conversions = new Lazy<IDictionary<TUnderlyingType, TConstant>>(() =>
            {
                var dict = new Dictionary<TUnderlyingType, TConstant>();
                foreach (var field in fields)
                {
                    var constant = (TConstant)field.GetValue(null);
                    var val = constant._value;
                    ValuesStore.Add(val);

                    if (ReferenceEquals(val, null))
                    {
                        _nullValuedConstant = constant;
                    }
                    else
                    {
                        dict[val] = constant;
                    }
                }
                return dict;
            });

            NameLookup = new Lazy<IDictionary<TConstant, string>>(() =>
            {
                var dict = new Dictionary<TConstant, string>();
                foreach (var field in fields)
                {
                    var constant = (TConstant)field.GetValue(null);
                    dict[constant] = field.Name;
                }
                return dict;
            });
        }

        /// <summary>
        /// Creates a new type safe constant value
        /// </summary>
        /// <param name="value">The value</param>
        protected TypeSafeConstantBase(TUnderlyingType value)
        {
            _value = value;

            if (!ReferenceEquals(_value, null))
            {
                _hashCode = value.GetHashCode();
            }
        }

        /// <summary>
        /// Gets the names of the type safe constant members
        /// </summary>
        public static IEnumerable<string> Names
        {
            get { return ParseLookup.Value.Keys; }
        }

        /// <summary>
        /// Gets the values of the type safe constant members
        /// </summary>
        public static IEnumerable<TUnderlyingType> Values
        {
            get { return ValuesStore; }
        }

        /// <summary>
        /// Converts a type safe constant to its underlying value type
        /// </summary>
        /// <param name="value">The type safe constant to convert</param>
        /// <returns>The underlying value of the constant</returns>
        public static explicit operator TUnderlyingType(TypeSafeConstantBase<TConstant, TUnderlyingType> value)
        {
            return value._value;
        }

        /// <summary>
        /// Determines whether or not two type safe constants are not value-equal
        /// </summary>
        /// <param name="left">The left type safe constant to check</param>
        /// <param name="right">The right type safe constant to check</param>
        /// <returns>true if the two type safe constants are not value-equal, false otherwise</returns>
        public static bool operator !=(TypeSafeConstantBase<TConstant, TUnderlyingType> left, TypeSafeConstantBase<TConstant, TUnderlyingType> right)
        {
            return !(left == right);
        }

        /// <summary>
        /// Determines whether or not two type safe constants are value-equal
        /// </summary>
        /// <param name="left">The left type safe constant to check</param>
        /// <param name="right">The right type safe constant to check</param>
        /// <returns>true if the two type safe constants are value-equal, false otherwise</returns>
        public static bool operator ==(TypeSafeConstantBase<TConstant, TUnderlyingType> left, TypeSafeConstantBase<TConstant, TUnderlyingType> right)
        {
            return ReferenceEquals(left, right) || (!ReferenceEquals(left, null) && left.Equals((TConstant)right));
        }

        /// <summary>
        /// Gets the type safe constant corresponding to the supplied name
        /// </summary>
        /// <param name="name">The name of the type safe constant to get</param>
        /// <returns>The type safe constant corresponding to the given name</returns>
        public static TConstant Parse(string name)
        {
            return ParseLookup.Value[name];
        }

        /// <summary>
        /// Gets the type safe constant corresponding to the supplied name, if it exists
        /// </summary>
        /// <param name="name">The name of the type safe constant to get</param>
        /// <param name="result">[Out] The type safe constant corresponding to the given name, if it exists, null otherwise</param>
        /// <returns>true if there was a member corresponding to the supplied name, false otherwise</returns>
        public static bool TryParse(string name, out TConstant result)
        {
            return ParseLookup.Value.TryGetValue(name, out result);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(TConstant other)
        {
            return ReferenceEquals(this, other) || (!ReferenceEquals(other, null) && Equals(other._value, _value));
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(TUnderlyingType other)
        {
            return Equals(other, _value);
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified object  is equal to the current object; otherwise, false.
        /// </returns>
        /// <param name="obj">The object to compare with the current object. </param><filterpriority>2</filterpriority>
        public override sealed bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null))
            {
                return false;
            }

            if (Equals(obj as TConstant))
            {
                return true;
            }

            return obj is TUnderlyingType && Equals((TUnderlyingType)obj);
        }

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override sealed int GetHashCode()
        {
            return _hashCode;
        }

        /// <summary>
        /// Returns a string that represents the current object.
        /// </summary>
        /// <returns>
        /// A string that represents the current object.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            string name;
            if (!NameLookup.Value.TryGetValue((TConstant)this, out name))
            {
                return "(Unknown)";
            }

            return name;
        }

        /// <summary>
        /// Converts a value of the underlying type for the type safe constant to its corresponding known value
        /// </summary>
        /// <param name="value">The value to convert</param>
        /// <returns>The converted value</returns>
        /// <remarks>Typically used in an explicit operator declared (as follows) in the derived type
        /// <code>public static explicit operator <typeparamref name="TConstant"/>(<typeparamref name="TUnderlyingType"/> value) { return Cast(value); }</code>
        /// </remarks>
        protected static TConstant Cast(TUnderlyingType value)
        {
            //Force Conversions to be created
            var conversions = Conversions.Value;

            if (ReferenceEquals(value, null))
            {
                return _nullValuedConstant ?? Make(default(TUnderlyingType));
            }

            TConstant result;
            if (!conversions.TryGetValue(value, out result))
            {
                return Make(value);
            }

            return result;
        }

        /// <summary>
        /// Creates new instances of the constant type with the specified underlying value
        /// </summary>
        /// <param name="value">The underlying value for the constant</param>
        /// <returns>The resulting type safe constant</returns>
        private static TConstant Make(TUnderlyingType value)
        {
            if (ReferenceEquals(ConstantConstructor, null))
            {
                var message = string.Format("{0} does not support adhoc values", typeof (TConstant).FullName);
                throw new InvalidOperationException(message);
            }

            return (TConstant)ConstantConstructor.Invoke(new object[] { value });
        }
    }
}